package cx3k.dataaccess.search;

import java.lang.reflect.Field;
//import java.lang.reflect.Method;
//import java.util.ArrayList;
import java.util.HashMap;
//import java.util.List;
import java.util.Map;

import cx3k.dataaccess.search.SearchValue.EqualityType;
import cx3k.scheduler.objects.Course;
import cx3k.scheduler.objects.GroupedCourse;
import cx3k.scheduler.objects.MeetingTime;
import cx3k.scheduler.objects.Profile;
import cx3k.scheduler.objects.RequirementGrouping;
import cx3k.scheduler.objects.Section;
import cx3k.scheduler.objects.Semester;

public class Search {
	
	private static final Map<Class<?>, TypeGrouping> COLLECTION_MAPPINGS;
	
	static{
		COLLECTION_MAPPINGS = new HashMap<Class<?>, TypeGrouping>();
		COLLECTION_MAPPINGS.put(Semester.class, new TypeGrouping("courses", Course.class));
		COLLECTION_MAPPINGS.put(Course.class, new TypeGrouping("sections", Section.class));
		COLLECTION_MAPPINGS.put(Section.class, new TypeGrouping("meetingTimes", MeetingTime.class));
		COLLECTION_MAPPINGS.put(RequirementGrouping.class, new TypeGrouping("groupedCourses", GroupedCourse.class));
	}
	
	private static class TypeGrouping{
		private String collectionName;
		private Class<?> type;
		
		private TypeGrouping(String collectionName, Class<?> type){
			this.collectionName = collectionName;
			this.type = type;
		}
		
		public String getCollectionName(){
			return collectionName;
		}
		
		public Class<?> getType(){
			return type;
		}
	}
	
	public class HQLQuery {

		private RootSearchNode root;
		
		private HQLQuery(RootSearchNode root){
			this.root = root;
		}
		
		public void and(HQLQuery other){
			getRoot().and(other.getRoot());
		}
		
		public void or(HQLQuery other){
			getRoot().or(other.getRoot());
		}
		
		public void join(HQLQuery other){	
			String path = "";
			
			try {
//				path = buildPath("", other.getRoot().getClassName(), Class.forName("cx3k.scheduler.objects." + getRoot().getClassName()));
				path = buildPath(other.getRoot().getClassName(), Class.forName("cx3k.scheduler.objects." + getRoot().getClassName()));
			} catch (ClassNotFoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
//			System.out.println("path = " + path);
			
			if(path == null){
				
				try {
					TypeGrouping type = COLLECTION_MAPPINGS.get(Class.forName("cx3k.scheduler.objects." + getRoot().getClassName()));
					if(type != null){
						String previousName = getRoot().getNickName();
						String collectionName = type.getCollectionName();
						Class<?> collectionType = type.getType();
						
						path = "";
						
						while(collectionType != null && !collectionType.getSimpleName().equals(other.getRoot().getClassName())){
							path += " join " + previousName + "." + collectionName + " as " + collectionName.toLowerCase().substring(0, collectionName.length() - 1);
							previousName = collectionName.toLowerCase().substring(0, collectionName.length() - 1);
							
							type = COLLECTION_MAPPINGS.get(collectionType);
							collectionName = type.getCollectionName();
							collectionType = type.getType();
						}
						
						path += " join " + previousName + "." + collectionName;
					} else {
						and(other);
						return;
					}
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				
				/*RootSearchNode temp = getRoot();
				root = other.getRoot();
				other.root = temp;
				
				try {
					path = buildPath(other.getRoot().getClassName(), Class.forName("cx3k.scheduler.objects." + getRoot().getClassName()));
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/
			} else {
				path = " join " + getRoot().getNickName() + "." + path;
			}
			
//			System.out.println("path2 = " + path);
			
//			getRoot().join(other.getRoot(), getRoot().getNickName() + "." + path);
			getRoot().join(other.getRoot(), path);
		}
		
				
		private String buildPath(String lookFor, Class<?> current){
			
			for(Field field : current.getDeclaredFields()){
				if(field.getType().getSimpleName().equals(lookFor)){
					return field.getName();
				} else {
					if(containsClassName(lookFor, field.getType(), 0)){
						return field.getName() + "." + buildPath(lookFor, field.getType());
					}
				}
			}
			
			return null;
		}
		
		private boolean containsClassName(String name, Class<?> current, int count){
			
//			System.out.println("checking " + current.getSimpleName());
			
//			String collectionName = name.substring(0, 1).toLowerCase() + name.substring(1) + "s";
			/*String collectionName = "";
			Class<?> collectionClass = null;
			if(COLLECTION_MAPPINGS.containsKey(current)){
				TypeGrouping type = COLLECTION_MAPPINGS.get(current);
				collectionName = type.getCollectionName();
				collectionClass = type.getType();
			}
			System.out.println("collection name = " + collectionName);*/
			
//			System.out.println("looking for " + name);
			
			
			
			for(Field field : current.getDeclaredFields()){
//				System.out.println("checking " + current.getSimpleName() + "." + field.getName());
				
//				System.out.println("comparing " + field.getName() + " to " + collectionName);
				
//				System.out.println(name);
				
				if(field.getType().getSimpleName().equals(name)){// || field.getName().equals(collectionName)){
					return true;
				} else if(field.getType().getPackage() != null && field.getType().getPackage().getName().equals("cx3k.scheduler.objects") && containsClassName(name, field.getType(), count++)){
					return true;
				} /*else if(field.getName().equals(collectionName)){
					if(collectionClass.getSimpleName().equals(name)){
						return true;
					} else {
						return containsClassName(name, collectionClass, count++);
					}
				}*/
				
				/*else if(field.getType().getTypeParameters()[0].getGenericDeclaration().getSimpleName().equals(name)){
					return true;
				}*/
			}
			
			
			
			
			/*try {
				String methodName = "get" + current.getSimpleName() + "s";
				System.out.println("method = " + methodName);
				Method method = current.getMethod(methodName);
				if(method != null){
					
					return true;
				}
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
			} catch (NoSuchMethodException e) {
				// TODO Auto-generated catch block
			}*/
			
			return false;
		}
		
		
		
		public String getHQL(){
			return getRoot().getDefaultQueryStr();
		}
		
		private RootSearchNode getRoot(){
			return root;
		}
		
		public String toString(){
			return getHQL();
		}
	}
	
	public HQLQuery getHQLQuery(){
		return new HQLQuery(getRoot());
	}
	
	private Class<?> type;
	
	private RootSearchNode root;
		
	public Search(Class<?> type){
		setType(type);
		setRoot(new RootSearchNode(getType()));
	}
	
	private void addSearchValue(SearchValue value){
		value.setParent(getRoot());
		getRoot().addChild(value);
	}
	
	private Class<?> getType(){
		return type;
	}
	
	private RootSearchNode getRoot(){
		return root;
	}

	private void setType(Class<?> type){
		this.type = type;
	}
	
	private void setRoot(RootSearchNode root){
		this.root = root;
	}
	
	public void addToQuery(Field field, String value){
		addToQuery(new SearchValue(getType(), field, value, getRoot()));
	}
	
	public void addToQuery(Field field, String value, EqualityType operator){
		addToQuery(new SearchValue(getType(), field, value, operator, getRoot()));
	}
	
	public void addToQuery(String field, String value){
		try {
			addToQuery(getType().getDeclaredField(field), value);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	} 
	
	public void addToQuery(String field, String value, EqualityType operator){
		try {
			addToQuery(getType().getDeclaredField(field), value, operator);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addAndToQuery(Field field, String value){
		addToQuery(new AndSearchValue(getType(), field, value, getRoot()));
	}
	
	public void addAndToQuery(Field field, String value, EqualityType operator){
		addToQuery(new AndSearchValue(getType(), field, value, operator, getRoot()));
	}
	
	public void addAndToQuery(String field, String value){
		try {
			addAndToQuery(getType().getDeclaredField(field), value);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addAndToQuery(String field, String value, EqualityType operator){
		try {
			addAndToQuery(getType().getDeclaredField(field), value, operator);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addOrToQuery(Field field, String value){
		addToQuery(new OrSearchValue(getType(), field, value, getRoot()));
	}
	
	public void addOrToQuery(Field field, String value, EqualityType operator){
		addToQuery(new OrSearchValue(getType(), field, value, operator, getRoot()));
	}
	
	public void addOrToQuery(String field, String value){
		try{
			addOrToQuery(getType().getDeclaredField(field), value);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}	
	public void addOrToQuery(String field, String value, EqualityType operator){
		try {
			addOrToQuery(getType().getDeclaredField(field), value, operator);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void addToQuery(SearchValue value){
		addSearchValue(value);
	}
	
	public String getQueryString(){
		return getRoot().getDefaultQueryStr();
	}
	
	public static void main(String[] args){
		
		Search searcher = new Search(Profile.class);
			
		searcher.addToQuery("profileId", "0", EqualityType.EQUALS);
		searcher.addOrToQuery("profileId", "2", EqualityType.EQUALS);
		searcher.addOrToQuery("profileId", "1");
		searcher.addAndToQuery("hash", "test");
		
		Search searcher2 = new Search(Course.class);
		
		searcher2.addToQuery("degreeProgram", "CMSC", EqualityType.LIKE);
		
		System.out.println(searcher.getQueryString());
		System.out.println(searcher2.getQueryString());
		
		RootSearchNode combo = searcher.getRoot();
		combo.and(searcher2.getRoot());
		
		System.out.println(combo.getDefaultQueryStr());
	}
}
